/**
 * 
 */
package ro.dta.idbi.patterns;

import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import ro.dta.idbi.interfaces.IExtentGenerator;
import ro.dta.idbi.interfaces.IntegrationOperation;
import ro.dta.idbi.model.AbstractPattern;
import ro.dta.idbi.model.AutoMedUtils;
import ro.dta.idbi.model.Utils;
import uk.ac.ic.doc.automed.IntegrityException;
import uk.ac.ic.doc.automed.NotFoundException;
import uk.ac.ic.doc.automed.modelmanagement.modeldef.SQLModelDef;
import uk.ac.ic.doc.automed.reps.Schema;
import uk.ac.ic.doc.automed.reps.SchemaObject;

/**
 * <b>Column to subtype transformation pattern</b><br />
 * 
 * Example of use:<br />
 * 
 * <code>col_to_subtype (&lt;&lt;table,col&gt;&gt;, &lt;&lt;newTable&gt;&gt;)</code>
 * <br />
 * 
 * Moves the column <i>col</i> to a new table <i>newTable</i>, that is a subtype
 * of the table <i>table</i>
 * 
 * @author Tudor Dobrila
 * 
 */
public class ColSubtype extends AbstractPattern {

	/**
	 * Name of the pattern
	 */
	public static final String NAME = "Column to Subtype";

	/**
	 * Description of the pattern
	 */
	public static final String DESCRIPTION = "Move column to subtype";

	/**
	 * Name of the command associated with the pattern
	 */
	public static final String COMMAND = "col_to_subtype";

	/**
	 * Integration phase during which the command is applied
	 */
	public static final EnumSet<IntegrationOperation> OPERATION = EnumSet.of(IntegrationOperation.CONFORMING,
			IntegrationOperation.IMPROVEMENT);

	/**
	 * Original column string
	 */
	private transient String origColStr;

	/**
	 * Original column (table, column)
	 */
	private transient Map.Entry<String, String> origCol;

	/**
	 * Name of the new table
	 */
	private transient String newTableStr;

	/**
	 * Definition of the column being moved as a child
	 */
	private Object[] colDef;

	/**
	 * Column to subtype pattern class constructor
	 * 
	 * @param sourceSchema
	 *            Source schema over which the transformation is applied
	 */
	public ColSubtype(final Schema sourceSchema) throws NotFoundException, IntegrityException {
		super(sourceSchema);

		pattern = Pattern.compile("^" + COMMAND + "[ ]*\\((" + COL_REGEX + ")" + COMMA_REGEX + "("
				+ TABLE_REGEX + ")\\)$");
	}

	/**
	 * Column to subtype pattern class constructor
	 * 
	 * @param sourceSchema
	 *            Name of the source schema over which the transformation is
	 *            applied
	 */
	public ColSubtype(final String sourceSchema) throws NotFoundException, IntegrityException {
		this(Schema.getSchema(sourceSchema));
	}

	/**
	 * Column to subtype pattern class constructor
	 * 
	 * @param sourceSchema
	 *            Name of the source schema over which the transformation is
	 *            applied
	 * @param origColStr
	 *            Column that is moved in a subtype
	 * @param newTableStr
	 *            Name of the table being introduced by the transformation
	 * @param colDef
	 *            Definition of the column being moved
	 */
	public ColSubtype(final Schema sourceSchema, final String origColStr, final String newTableStr,
			final Object[] colDef) throws NotFoundException, IntegrityException {
		this(sourceSchema);

		this.origColStr = origColStr;
		this.origCol = parseColName(origColStr);
		this.newTableStr = newTableStr;
		this.colDef = colDef;
	}

	@Override
	protected List<String> getPrimitiveCommands() throws NotFoundException, IntegrityException {
		List<String> result = new ArrayList<String>();

		final String tableName = "<<" + origCol.getKey() + ">>";
		final SchemaObject origTable = sourceSchema.getSchemaObject(tableName);
		final List<String> pks = AutoMedUtils.getPK(sourceSchema, origTable);
		final IExtentGenerator gen = new ExtentGenerator(origColStr, origCol, newTableStr, pks);
		StringBuilder cmd = new StringBuilder();
		String iql;

		// Step 1: add new table
		iql = gen.getExtentsForStep(1).get(0);
		cmd.append("add (");
		cmd.append(Utils.genTableRepresentation(newTableStr));
		cmd.append(", ").append(iql).append(")");
		result.add(cmd.toString());

		// Step 2: create PK columns to newly created table
		Object[] pkArr = new Object[2 + pks.size()];
		pkArr[0] = newTableStr + "_pk";
		pkArr[1] = newTableStr;
		int crtPos = 2;
		List<String> extents = gen.getExtentsForStep(2);
		for (int i = 0; i < pks.size(); i++) {
			final SchemaObject pkCol = sourceSchema.getSchemaObject("<<" + origCol.getKey() + ","
					+ pks.get(i) + ">>");
			final Object[] pkDef = pkCol.getSchemeDefinition();
			cmd.setLength(0);
			cmd.append("add (");
			cmd.append(Utils.genColRepresentation(newTableStr, pks.get(i), pkDef[2], pkDef[3]));
			cmd.append(",").append(extents.get(i)).append(")");
			result.add(cmd.toString());
			pkArr[crtPos++] = "<<" + newTableStr + "," + pks.get(i) + ">>";
		}

		// Step 3: add PK constraint to newly created table
		if (sqlModel.isFeatureInUse(SQLModelDef.PRIMARY_KEY)) {
			cmd.setLength(0);
			cmd.append("add (");
			cmd.append(Utils.genPKRepresentation(pkArr));
			cmd.append(")");
			result.add(cmd.toString());
		}

		// Step 4: create FK constraint from newly created table to original
		// table
		if (sqlModel.isFeatureInUse(SQLModelDef.FOREIGN_KEY)) {
			cmd.setLength(0);
			cmd.append("add (");
			cmd.append(Utils.genFKRepresentation(newTableStr, origCol.getKey(), pks));
			cmd.append(")");
			result.add(cmd.toString());
		}

		// Step 5: create column in subtype
		iql = gen.getExtentsForStep(4).get(0);
		cmd.setLength(0);
		cmd.append("add (");
		cmd.append(Utils.genColRepresentation(newTableStr, origCol.getValue(), "notnull", colDef[3]));
		cmd.append(",").append(iql).append(")");
		result.add(cmd.toString());

		// Step 6: remove original column from original table
		iql = gen.getExtentsForStep(6).get(0);
		cmd.setLength(0);
		cmd.append("delete (");
		cmd.append(Utils.genColRepresentation(origCol.getKey(), origCol.getValue(), colDef[2], colDef[3]));
		cmd.append(",").append(iql).append(")");
		result.add(cmd.toString());

		return result;
	}

	@Override
	protected boolean verify() {
		// TODO Auto-generated method stub
		return true;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see ro.dta.idbi.model.TransformationPattern#parse(java.lang.String)
	 */
	@Override
	protected void parse(final String trans) {
		final Matcher matcher = pattern.matcher(trans);
		matcher.find();

		origColStr = matcher.group(1);
		origCol = parseColName(origColStr);
		newTableStr = parseTableName(matcher.group(2));
		try {
			final SchemaObject colObj = sourceSchema.getSchemaObject(origColStr);
			colDef = colObj.getSchemeDefinition();
		} catch (NotFoundException e) {
			throw new IllegalArgumentException("Schema object " + origColStr + " does not exist.");
		}
	}

	/**
	 * @return The original column being moved in a subtype
	 */
	public String getOrigColStr() {
		return origColStr;
	}

	/**
	 * @return The new table being created as a subtype of the table containing
	 *         the original column
	 */
	public String getNewTableStr() {
		return newTableStr;
	}

	/**
	 * Extent generator for the <i>Column to subtype transformation pattern</i>
	 * 
	 * @author Tudor Dobrila
	 * 
	 */
	static class ExtentGenerator implements IExtentGenerator {

		private final String origColStr;
		private final Entry<String, String> origCol;
		private final String newTableStr;
		private final List<String> pks;

		public ExtentGenerator(final String origColStr, final Map.Entry<String, String> origCol,
				final String newTableStr, final List<String> pks) {
			this.origColStr = origColStr;
			this.origCol = origCol;
			this.newTableStr = newTableStr;
			this.pks = pks;
		}

		@Override
		public List<String> getExtentsForStep(int step) {
			List<String> result = new ArrayList<String>();
			StringBuilder iql = new StringBuilder();

			switch (step) {
			case 1:
				// Step 1: add new table
				iql.append("[ {");
				iql.append("x").append("} | {x,y}");
				iql.append(" <- ").append(origColStr).append("; y <> Null]");
				result.add(iql.toString());
				break;
			case 2:
				// Step 2: create PK columns to newly created table
				for (String pk : pks) {
					iql.setLength(0);
					iql.append("[ {x,y} | {x,y} <- <<").append(origCol.getKey()).append(",");
					iql.append(pk).append(">>; {x,z} <- ").append(origColStr);
					iql.append("; z <> Null ]");
					result.add(iql.toString());
				}
				break;
			case 4:
				// Step 4: create original column
				iql.append("[ {x,y} | {x} <- <<").append(newTableStr).append(">>; ");
				iql.append("{x,y} <- ").append(origColStr).append("; y <> Null ]");
				result.add(iql.toString());
				break;
			case 6:
				// Step 6: remove original column from original table
				iql.append("<<").append(newTableStr).append(",").append(origCol.getValue()).append(">> ++ ");
				iql.append("[ {x,Null} | {x} <- <<").append(origCol.getKey()).append(">> -- <<");
				iql.append(newTableStr).append(">>").append(" ]");
				result.add(iql.toString());
				break;
			default:
			}
			return result;
		}
	}
}
